home *** CD-ROM | disk | FTP | other *** search
/ Info-Mac 3 / Info_Mac_1994-01.iso / Development / Source / FloatWin Source next >
Text File  |  1993-06-18  |  15KB  |  317 lines

  1. unit Floating;
  2.  
  3. {A set of routines to handle floating windows. For use with THINK Pascal 4.0}
  4. {Written by F. Pottier, june 1993. E-mail : pottier@clipper.ens.fr on Internet}
  5.  
  6. {This file looks best when viewed with THINK Pascal's pretty printer. I use Geneva 9 and 4 space tabs.}
  7.  
  8. {Originally based on a set of C routines written by Patrick Doane (America OnLine : Patrick5) }
  9. {Thanks go to him, and also to Troy Gaul for placing his Infinity Windoid in the public domain}
  10.  
  11. {This code is placed in the public domain. It may be used by anybody, for any purposes. It would be kind to give credits to me}
  12. {as well as to the aforementioned people, though.}
  13. {If you experience any problems, or have ideas for enhancements, tell me about it. I can't guarantee I'll spend much}
  14. {time on it, since I'm not a professional programmer, but I'll take a look at it.}
  15.  
  16. { --- Some general comments ------------------------------------------------------------------------------------------ }
  17.  
  18. {The Window Manager doesn't know about floating windows. To obtain floating windows in a program,}
  19. {one has to handle them 'by hand', which means calling low-level Window Manager routines to place the windows}
  20. {in their correct positions after each event.}
  21. {The frequently used, and now to be avoided, Window Manager routines are :}
  22. {        SelectWindow        }
  23. {        CloseWindow            }
  24. {        HideWindow            }
  25. {        ShowWindow            }
  26. {        DisposeWindow        }
  27. {        DragWindow            }
  28. {Those routines don't know about floating windows, so calling them would put the floating windows behind normal windows.}
  29. {You also have to be careful with GetNewWindow. You can't call GetNewWindow(ID, wStorage, Pointer(-1)) because that}
  30. {would put the new window in the front. Rather, we call GetNewWindow(ID, wStorage, nil) which places the window in the}
  31. {back, and then call our home-made routine, SelectTheWindow.}
  32.  
  33. {At every point in the program, we maintain three global variables : topFloat, bottomFloat and topWindow.}
  34. {topFloat holds the top floating window, nil if there is none. Same thing for bottomFloat with the bottom floating window,}
  35. {and for topWindow with the top regular window. These three global variables, along with the Window Manager's window}
  36. {list, are enough to handle the behavior of our windows.}
  37. {Hidden windows are placed behind all others, so they don't count when determining those variables' values.}
  38.  
  39. {In order to distinguish between floating windows and regular ones, we choose a different windowKind for the former.}
  40.  
  41. {To make hiliting and unhiliting floating windows simpler, I chose to use a WDEF that always draws windoids in a hilited}
  42. {state (e.g. Infinity Windoid with the Always Hilite option). This way, I don't have to worry at all about it.}
  43.  
  44. {A final remark about modeless dialogs : modeless dialogs and floating windows don't go well together. The Dialog Manager}
  45. {is confused by floating windows. IsDialogEvent and DialogSelect don't work because they use FrontWindow to find the dialog.}
  46. {It is easy to rewrite a modified version of isDialogEvent. It is already trickier for DialogSelect. Maybe it would be simpler}
  47. {to use windows with controls instead of modeless dialogs. Or maybe it's possible to have DialogSelect and isDialogEvent}
  48. {work by patching FrontWindow ? }
  49. {If somebody comes up with a good idea on this topic, please tell me about it.}
  50.  
  51. { --- How to use the unit, in short -------------------------------------------------------------------------------------- }
  52.  
  53. {- Call InitFloats once before opening any windows.}
  54. {- To create a new window, call GetNewWindow(id, storage, NIL). Then call SelectTheWindow if it's a regular window,}
  55. {   otherwise call MakeFloat.}
  56. {- To determine whether a given window is a floating one, call isFloating}
  57. {- Instead of SelectWindow[and ShowWindow], HideWindow, DisposeWindow, DragWindow, use SelectTheWindow, HideTheWindow,}
  58. {   DIsposeTheWindow and DragTheWindow.}
  59. {- Upon a receiving a suspend event, you may call HideFloats. Use ShowFloats when receiving a resume event.}
  60. {- Instead of calling FrontWindow, you can read the values of the variables topFloat and topWindow. They should be up-to-date}
  61. {   at any point in the program.}
  62.  
  63. { --- Unit interface --------------------------------------------------------------------------------------------------- }
  64.  
  65. interface
  66.  
  67.     procedure InitFloats;                                                                            {initializes the global variables}
  68.  
  69.     function IsFloating (whichWindow: WindowPtr): Boolean;                        {tells whether a window is floating}
  70.     procedure MakeFloat (theWindow: WindowPtr);                                    {turns a newly created window into a floating window}
  71.  
  72.     procedure HideTheWindow (whichWindow: WindowPtr);                        {equivalents for the old Toolbox calls}
  73.     procedure DisposeTheWindow (whichWindow: WindowPtr);
  74.     procedure SelectTheWindow (whichWindow: WindowPtr);
  75.     procedure DragTheWindow (whichWindow: WindowPtr; event: EventRecord);
  76.  
  77.     procedure HideFloats;                                                                            {hides all floating windows}
  78.     procedure ShowFloats;                                                                        {shows all floating windows}
  79.  
  80.     var
  81.         bottomFloat, topFloat, topWindow: WindowPtr;
  82.  
  83. { -------------------------------------------------------------------------------------------------------------------- }
  84.  
  85. implementation
  86.  
  87.     const
  88.         kFloatingKind = 317;                                                                            {arbitrary constant for floating windows' windowKind}
  89.  
  90. {ActivateWindow hilites/unhilites the window and then generates an activate/deactivate event for it.}
  91. {In order to generate activate events, it mucks with low-mem globals. It would be cleaner to directly call the activate}
  92. {routine for the window. The advantage of this method is that it also works with modeless dialogs : the Dialog Manager}
  93. {doesn't know that a dialog is active unless it actually receives an activate event.}
  94.  
  95.     procedure ActivateWindow (window: WindowPtr; on: Boolean);
  96.         const
  97.             CurActivate = $0A64;                                                                    {writing a WindowPtr in those globals generates}
  98.             CurDeactive = $0A68;                                                                    {an activate or deactivate event}
  99.         var
  100.             p: ^WindowPtr;
  101.     begin
  102.         if window <> nil then begin
  103.             HiliteWindow(window, on);                                                            {hilite the window}
  104.             if on then
  105.                 p := Pointer(CurActivate)                                                            {generate the appropriate event}
  106.             else
  107.                 p := Pointer(CurDeactive);
  108.             p^ := window;
  109.         end;
  110.     end;
  111.  
  112. {InitFloats is to be called at the beginning of the program, before creating any windows. It sets our global variables to nil.}
  113.  
  114.     procedure InitFloats;
  115.     begin
  116.         topFloat := nil;
  117.         bottomFloat := nil;
  118.         topWindow := nil;
  119.     end;
  120.  
  121. {IsFloating and isVisible are little, foolprof routines that tell whether a window is floating or visible.}
  122. {Using them avoids errors such as examining WindowPeek(whichWindow)^.visible when whichWindow = nil}
  123.  
  124.     function IsFloating (whichWindow: WindowPtr): Boolean;
  125.     begin
  126.         if whichWindow = nil then
  127.             IsFloating := false
  128.         else
  129.             IsFloating := WindowPeek(whichWindow)^.windowKind = kFloatingKind;
  130.     end;
  131.  
  132.     function isVisible (whichWindow: WindowPtr): Boolean;
  133.     begin
  134.         if whichWindow = nil then
  135.             isVisible := false
  136.         else
  137.             isVisible := WindowPeek(whichWindow)^.visible;
  138.     end;
  139.  
  140. {UpdateFloats updates the topFloat and bottomFloat variables by walking down the window list. It then determines}
  141. {topWindow by taking the first visible window after bottomFloat.}
  142.  
  143.     procedure UpdateVars;
  144.         var
  145.             theWindow: WindowPtr;
  146.     begin
  147.         theWindow := FrontWindow;                                                            {start from the frontmost window}
  148.         if IsFloating(theWindow) then begin                                            {if it's floating, then it's topFloat}
  149.             topFloat := theWindow;
  150.             while (theWindow <> nil) and isFloating(theWindow) do begin    {walk down the list until we find a regular window}
  151.                 bottomFloat := theWindow;                                                    {or the end of the list}
  152.                 theWindow := WindowPtr(WindowPeek(theWindow)^.nextWindow);
  153.             end;
  154.  
  155.             while (theWindow <> nil) and (not isVisible(theWindow)) do    {find the next visible window after bottomFloat}
  156.                 theWindow := WindowPtr(WindowPeek(theWindow)^.nextWindow);
  157.             topWindow := theWindow;
  158.         end
  159.         else begin                                                                                    {if the frontmost window is a regular one, then there}
  160.             topFloat := nil;                                                                            {are no floating windows and topWindow = FrontWindow}
  161.             bottomFloat := nil;
  162.             topWindow := FrontWindow;
  163.         end;
  164.     end;
  165.  
  166. {MakeFloat turns a newly created window into a floating window and brings it to the front}
  167. {The window must have been created via a call to [Get]NewWindow(id, wStorage, nil).}
  168.  
  169.     procedure MakeFloat (theWindow: WindowPtr);
  170.     begin
  171.         BringToFront(theWindow);                                                                {Bring the window to the front without unhiliting other}
  172.         ShowHide(theWindow, true);                                                            {windows. Make it visible in case it isn't}
  173.         if topFloat = nil then
  174.             bottomFloat := theWindow;                                                        {update bottomFloat et topFloat}
  175.         topFloat := theWindow;
  176.         WindowPeek(theWindow)^.windowKind := kFloatingKind;                    {remember that this window should float}
  177.     end;
  178.  
  179. {ReactToRemoval handles a few necessary steps after killing whichWindow. underWindow is the window that was}
  180. {under whichWindow (i.e. next to it in the window list).}
  181.  
  182.     procedure ReactToRemoval (underWindow: WindowPeek);
  183.     begin
  184.         while (underWindow <> nil) and (not isVisible(WindowPtr(underWindow))) do
  185.             underWindow := underWindow^.nextWindow;                                {activate the next visible window under whichWindow}
  186.         ActivateWindow(WindowPtr(underWindow), true);
  187.         UpdateVars;                                                                                    {update the variables}
  188.     end;
  189.  
  190.     procedure HideTheWindow (whichWindow: WindowPtr);
  191.         var
  192.             underWindow: WindowPeek;                                                        {we must determine which window is under whichWindow}
  193.     begin                                                                                                {before hiding it, because HideWindow sends it to the back}
  194.         underWindow := WindowPeek(whichWindow)^.nextWindow;                {thus changing the value of nextWindow}
  195.         HideWindow(whichWindow);
  196.         ReactToRemoval(underWindow);
  197.     end;
  198.  
  199.     procedure DisposeTheWindow (whichWindow: WindowPtr);
  200.         var
  201.             underWindow: WindowPeek;
  202.     begin
  203.         underWindow := WindowPeek(whichWindow)^.nextWindow;                {same thing as HideTheWindow, with DisposeWindow instead}
  204.         DisposeWindow(whichWindow);
  205.         ReactToRemoval(underWindow);
  206.     end;
  207.  
  208. {SelectTheWindow is the most important routine. Here are the steps to be taken :}
  209. {If whichWindow is not visible, show it.}
  210. {- If whichWindow is a floating window, just bring it to the front.}
  211. {- If it is a regular window and there are no floating windows, bring it to the front, activate it, deactivate the former front window}
  212. {- If it is a regular window and there are floating windows, send it behind bottomFloat, activate it, deactivate the old topWindow}
  213. {Finally, update the global variables.}
  214.  
  215.     procedure SelectTheWindow (whichWindow: WindowPtr);
  216.         var
  217.             wPeek: WindowPeek;
  218.     begin
  219.         wPeek := WindowPeek(whichWindow);
  220.  
  221.         if not wPeek^.visible then                                                            {make the window visible if necessary}
  222.             ShowHide(whichWindow, true);
  223.  
  224.         if IsFloating(whichWindow) then                                                     {a floating window just needs to be brought to the front}
  225.             BringToFront(whichWindow)
  226.         else if whichWindow <> topWindow then begin                            {regular window. If it isn't topWindow we have to react}
  227.             if bottomFloat <> nil then begin                                                {if there are floating windows, then send whichWindow}
  228.                 SendBehind(whichWindow, bottomFloat);                                {behind bottomFloat}
  229.                 PaintOne(wPeek, wPeek^.strucRgn);                                        {These two low-level calls are necessary to generate}
  230.                 CalcVis(wPeek);                                                                    {update events and maintain the windows' visRgns}
  231.             end
  232.             else
  233.                 BringToFront(whichWindow);                                                    {no floating windows : just call BringToFront}
  234.  
  235.             ActivateWindow(whichWindow, true);                                        {activate the new front window}
  236.             ActivateWindow(topWindow, false);                                            {and deactivate the old one}
  237.         end;
  238.  
  239.         UpdateVars;                                                                                    {finally update our vars}
  240.     end;
  241.  
  242. {DragTheWindow is to be called instead of DragWindow, because DragWindow brings the window to the front unless the command}
  243. {key is down. We use DragGrayRgn to drag a gray outline of the window. We clip the dragging to a region consisting of the whole}
  244. {desktop, minus the windows that are in front of the window we're dragging.}
  245. {To emulate DragWindow's behavior, we send the window to the front, unless the command key is down.}
  246.  
  247.     procedure DragTheWindow (whichWindow: WindowPtr; event: EventRecord);
  248.         const
  249.             cancelDrag = $80008000;                                                            {returned by DragGrayRgn if the user aborts the drag}
  250.         var
  251.             savePort: GrafPtr;
  252.             wPort: GrafPort;
  253.             thePoint: point;
  254.             newLoc: longint;
  255.             theWindow: WindowPeek;
  256.             dragRect: rect;
  257.             dragRgn: RgnHandle;
  258.     begin
  259.         if BitAnd(event.modifiers, cmdKey) = 0 then                                {unless the command key is depressed, select the window}
  260.             SelectTheWindow(whichWindow);                                                {before doing the dragging}
  261.  
  262.         GetPort(savePort);                                                                        {save the grafport}
  263.         SetPort(whichWindow);
  264.         thePoint := whichWindow^.portRect.topLeft;
  265.         LocalToGlobal(thePoint);                                                                {remember the window's original position}
  266.  
  267.         dragRgn := NewRgn;                                                                        {put the window's strucRgn in a new region}
  268.         CopyRgn(WindowPeek(whichWindow)^.strucRgn, dragRgn);                {we can't pass the strucRgn directly to DragGrayRgn}
  269.                                                                                                             {because DragGrayRgn modifies the region}
  270.         OpenPort(@wPort);                                                                        {open a port to draw the gray outline}
  271.         CopyRgn(GetGrayRgn, wPort.visRgn);                                            {set its visRgn to the whole screen, minus the strucRgns}
  272.         theWindow := WindowPeek(FrontWindow);                                        {of all the windows in front of whichWindow}
  273.         while theWindow <> WindowPeek(whichWindow) do begin
  274.             DiffRgn(wPort.visRgn, theWindow^.strucRgn, wPort.visRgn);
  275.             theWindow := theWindow^.nextWindow;
  276.         end;
  277.  
  278.         dragRect := GetGrayRgn^^.rgnBBox;
  279.         InsetRect(dragRect, 4, 4);
  280.  
  281.         newLoc := DragGrayRgn(dragRgn, event.where, dragRect, dragRect, noConstraint, nil);
  282.         if (newLoc <> cancelDrag) and (newLoc <> 0) then
  283.             MoveWindow(whichWindow, thePoint.h + LoWord(newLoc), thePoint.v + HiWord(newLoc), false);
  284.  
  285.         DisposeRgn(dragRgn);                                                                    {dispose of the region and of the port}
  286.         ClosePort(@wPort);
  287.         SetPort(savePort);                                                                        {then restore the old grafPort and exit}
  288.     end;
  289.  
  290. {HideFloats/ShowFloats are simple routines that hide/show all floating windows. They're useful to handle suspend/resume events.}
  291.  
  292.     procedure HideFloats;
  293.     begin
  294.         while isFloating(FrontWindow) do                                                {calling FrontWindow twice is not very efficient, but}
  295.             HideWindow(FrontWindow);                                                        {it's shorter!}
  296.         topFloat := nil;
  297.         bottomFloat := nil;
  298.     end;
  299.  
  300.     procedure ShowFloats;
  301.         const
  302.             WindowList = $09D6;                                                                {this low-mem variable contains the address of the}
  303.         var                                                                                                {first window in the Window Manager's list}
  304.             theWindow, nextWindow: WindowPeek;
  305.             p: ^WindowPeek;
  306.     begin
  307.         p := Pointer(WindowList);
  308.         theWindow := WindowPeek(p^);                                                        {find the first window in the Window Manager's list}
  309.         while (theWindow <> nil) do begin                                                {walk down the list...}
  310.             nextWindow := theWindow^.nextWindow;
  311.             if isFloating(WindowPtr(theWindow)) then
  312.                 SelectTheWindow(WindowPtr(theWindow));                            {..sending every floating window to the front}
  313.             theWindow := nextWindow;
  314.         end;
  315.     end;
  316.  
  317. end.